package com.kurt.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kurt.annotation.Log;
import com.kurt.entity.SysOperLog;
import com.kurt.service.SysOperLogService;
import com.kurt.util.UserContext;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.Map;

/**
 * 操作日志记录处理
 *
 * @author example
 * @date 2024-03-20
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspect {

    private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>();

    private final ObjectMapper objectMapper;
    private final SysOperLogService operLogService;

    /**
     * 处理请求前执行
     */
    @Before("@annotation(logAnnotation)")
    public void doBefore(JoinPoint joinPoint, Log logAnnotation) {
        START_TIME.set(System.currentTimeMillis());
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint     切点
     * @param logAnnotation 日志注解
     * @param jsonResult    返回结果
     */
    @AfterReturning(pointcut = "@annotation(logAnnotation)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log logAnnotation, Object jsonResult) {
        handleLog(joinPoint, logAnnotation, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "@annotation(logAnnotation)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log logAnnotation, Exception e) {
        handleLog(joinPoint, logAnnotation, e, null);
    }

    protected void handleLog(JoinPoint joinPoint, Log logAnnotation, Exception e, Object jsonResult) {
        try {
            // 获取当前的用户信息
            String username = UserContext.getUsername();
            if (!StringUtils.hasText(username)) {
                username = "anonymous";
            }

            // 获取请求信息
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            if (requestAttributes == null) {
                return;
            }
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();

            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();

            // 创建操作日志对象
            SysOperLog operLog = new SysOperLog();
            operLog.setStatus(e != null ? 1 : 0);
            operLog.setOperTime(LocalDateTime.now());
            operLog.setOperName(username);
            operLog.setOperUrl(request.getRequestURI());
            operLog.setMethod(className + "." + methodName + "()");
            operLog.setRequestMethod(request.getMethod());
            operLog.setOperIp(getClientIp(request));
            operLog.setOperLocation("未知"); // TODO: 根据IP获取操作地点

            // 设置请求参数
            if (logAnnotation.isSaveRequestData()) {
                setRequestValue(joinPoint, operLog, logAnnotation.excludeParamNames());
            }

            // 设置返回参数
            if (logAnnotation.isSaveResponseData() && jsonResult != null) {
                operLog.setJsonResult(objectMapper.writeValueAsString(jsonResult));
            }

            // 设置注解上的参数
            operLog.setTitle(logAnnotation.title());
            operLog.setBusinessType(logAnnotation.businessType());
            operLog.setOperatorType(logAnnotation.operatorType());

            // 设置异常信息
            if (e != null) {
                operLog.setStatus(1);
                String errorMsg = e.getMessage();
                if (StringUtils.hasText(errorMsg) && errorMsg.length() > 2000) {
                    errorMsg = errorMsg.substring(0, 2000);
                }
                operLog.setErrorMsg(errorMsg);
            }

            // 设置消耗时间
            operLog.setCostTime(System.currentTimeMillis() - START_TIME.get());

            // 异步保存日志
            operLogService.asyncInsertOperLog(operLog);
        } catch (Exception ex) {
            log.error("记录操作日志失败：", ex);
        } finally {
            START_TIME.remove();
        }
    }

    /**
     * 设置请求参数
     */
    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception {
        Map<String, String[]> paramMap = null;
        if (joinPoint.getArgs().length > 0 && HttpServletRequest.class.isAssignableFrom(joinPoint.getArgs()[0].getClass())) {
            HttpServletRequest request = (HttpServletRequest) joinPoint.getArgs()[0];
            paramMap = request.getParameterMap();
        }

        if (paramMap == null && joinPoint.getArgs().length > 0) {
            Object[] args = joinPoint.getArgs();
            Object params = processArgs(args, excludeParamNames);
            operLog.setOperParam(objectMapper.writeValueAsString(params));
        } else if (paramMap != null) {
            operLog.setOperParam(objectMapper.writeValueAsString(paramMap));
        }
    }

    /**
     * 处理请求参数
     */
    private Object processArgs(Object[] args, String[] excludeParamNames) {
        Object params = null;
        try {
            if (args != null && args.length > 0) {
                if (args.length == 1) {
                    Object arg = args[0];
                    if (arg instanceof MultipartFile
                            || arg instanceof HttpServletRequest
                            || arg instanceof BindingResult) {
                        return null;
                    }
                    params = arg;
                } else {
                    params = args;
                }
            }
        } catch (Exception e) {
            log.error("处理请求参数异常：", e);
        }
        return params;
    }

    /**
     * 获取客户端IP地址
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
            // 多个IP时，第一个为真实IP
            int index = ip.indexOf(',');
            if (index != -1) {
                ip = ip.substring(0, index);
            }
            return ip.trim();
        }

        ip = request.getHeader("Proxy-Client-IP");
        if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
            return ip.trim();
        }

        ip = request.getHeader("WL-Proxy-Client-IP");
        if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
            return ip.trim();
        }

        ip = request.getRemoteAddr();
        return ip;
    }
} 